package small_file

import (
	"context"
	"crypto/md5"
	"encoding/hex"
	"enode/store/small_file/db"
	"enode/store/small_file/models"
	"enode/store/small_file/util"
	"errors"
	"fmt"
	"io"
	"os"
	"path"
)

type FileStore struct {
	sqlHandle SqlHandle
	rootPath  string
	useMd5    bool
}

func NewFileStore(ctx context.Context, root string) *FileStore {
	dbPath := path.Join(root, "sqlite.db")
	sql := db.NewSqliteHandle(dbPath)
	sql.MigratorTable(ctx)
	return &FileStore{
		sqlHandle: sql,
		rootPath:  root,
		useMd5:    true,
	}
}

//CleanData 测试时使用，清理数据表中数据
func (f *FileStore) CleanData() {
	f.sqlHandle.CleanData()
}

//todo 改为 md5
func getMd5(input string) string {
	h := md5.New()
	h.Write([]byte(input))
	b := h.Sum(nil)
	return hex.EncodeToString(b)
}
func (f *FileStore) SavePieceToBlob(ctx context.Context, pieceId string, reader io.Reader, expire int, size int64) error {
	storeName := "1"
	expireFolder := util.FolderNameAfter(expire)
	if len(pieceId) < 2 {
		return errors.New("error pieceid")
	}
	sub := f.getPieceSubdirName(pieceId)

	savedir := path.Join(f.rootPath, expireFolder, sub)
	if err := os.MkdirAll(savedir, os.ModePerm); err != nil {
		return err
	}
	storePath := path.Join(savedir, storeName)
	startPos, err := f.sqlHandle.AppendToBlob(ctx, storePath, expireFolder, int64(size))
	if err != nil {
		return err
	}
	blobFile, err := os.OpenFile(storePath, os.O_WRONLY|os.O_CREATE, os.ModePerm)
	if err != nil {
		return err
	}
	defer blobFile.Close()
	_, err = blobFile.Seek(int64(startPos), io.SeekCurrent)
	fmt.Printf("save %s to %s from pos %d\n", pieceId, storePath, startPos)
	if err != nil {
		return err
	}
	n, err := io.Copy(blobFile, reader)
	if err != nil {
		return err
	}
	if n != size {
		fmt.Println("copy size less than request")
	}
	//fmt.Println("copy size", n, "startPos", startPos)
	err = f.sqlHandle.InserFileIndex(ctx, models.FileIndex{
		PieceId:   pieceId,
		StoreName: storeName,
		Expire:    expireFolder,
		Size:      size,
		StartPos:  startPos,
		EndPos:    startPos + int64(size),
		Del:       false,
	})
	if err != nil {
		return err
	}
	return nil
}
func (f *FileStore) getPieceSubdirName(pieceId string) string {
	sub := pieceId[:2]
	if f.useMd5 {
		sub = getMd5(pieceId)[:2]
	}
	return sub
}
func (f *FileStore) ReadPieceFromBlob(ctx context.Context, pieceId string) ([]byte, error) {

	fileIndex, err := f.sqlHandle.GetFileInfo(ctx, pieceId)
	if err != nil {
		return nil, err
	}
	if len(pieceId) < 2 {
		return nil, errors.New("error pieceid")
	}
	sub := f.getPieceSubdirName(pieceId)

	storePath := path.Join(f.rootPath, fileIndex.Expire, sub, fileIndex.StoreName)
	fmt.Println("read storePath", storePath)
	file, err := os.OpenFile(storePath, os.O_RDONLY, os.ModePerm)
	if err != nil {
		return nil, err
	}
	_, err = file.Seek(fileIndex.StartPos, io.SeekCurrent)
	if err != nil {
		return nil, err
	}
	buf := make([]byte, fileIndex.Size)
	n, err := file.Read(buf)
	if err != nil {
		return nil, err
	}
	return buf[:n], nil
}
func (f *FileStore) DelPieceFromBlob(ctx context.Context, pieceId string) error {
	return f.sqlHandle.MarkDel(ctx, pieceId)
}

func (f *FileStore) CleanExpireFiles(ctx context.Context, expire string) error {
	fmt.Println("CleanExpireFiles", expire)
	if len(f.rootPath) == 0 {
		return errors.New("root is empty")
	}
	err := f.sqlHandle.CleanExpire(ctx, expire)
	dirToRemove := path.Join(f.rootPath, expire)
	err = os.RemoveAll(dirToRemove)
	return err
}

func (f *FileStore) PutPiece(ctx context.Context, key string, expire int, size int64, r io.Reader) (hash []byte, err error) {
	err = f.SavePieceToBlob(ctx, key, r, expire, size)
	return
}
func (f *FileStore) GetPiece(ctx context.Context, key string) (data []byte, err error) {
	return f.ReadPieceFromBlob(ctx, key)
}
func (f *FileStore) DelPiece(ctx context.Context, key string) (err error) {
	return f.DelPieceFromBlob(ctx, key)
}
func (f *FileStore) Mode() string {
	return ""
}
